#ifndef     __TIMER_H
#define     __TIMER_H

#include "./compat.h"

namespace device::timer {

#define TIMER(BASE)     reinterpret_cast<TIM_TypeDef *>(BASE)

constexpr uint32_t __72M__ = 72000000;
constexpr uint32_t __36M__ = 36000000;
constexpr uint32_t __1M__ = 1000000;

template <uint32_t BASE, uint32_t PSC_1M, uint8_t CHANNEL>
struct PWMTimer {

    inline static void init() {
        static_assert( (CHANNEL >= 1) && (CHANNEL <= 4), "CHANNEL fail");

        TIMER(BASE)->CR1 = 0x00;
        TIMER(BASE)->CR2 = 0x00;
        TIMER(BASE)->PSC = PSC_1M;
        TIMER(BASE)->CNT = 0;

        if constexpr (CHANNEL == 1) {
            TIMER(BASE)->CCMR1 = 0x0060;
            TIMER(BASE)->CCER = 0x0001;
        } else if constexpr (CHANNEL == 2) {
            TIMER(BASE)->CCMR1 = 0x6000;
            TIMER(BASE)->CCER = 0x0010;
        } else if constexpr (CHANNEL == 3) {
            TIMER(BASE)->CCMR2 = 0x0060;
            TIMER(BASE)->CCER = 0x0100;
        } else if constexpr (CHANNEL == 4) {
            TIMER(BASE)->CCMR2 = 0x6000;
            TIMER(BASE)->CCER = 0x1000;
        }
    }

    inline static void start_with_irq(uint32_t base_freq) {
        init();
        set_pwm_freq(base_freq);
        TIMER(BASE)->DIER = TIM_DIER_UIE;
        start();
    }

    inline static void start_with_no_irq(uint32_t base_freq) {
        init();
        set_pwm_freq(base_freq);
        TIMER(BASE)->DIER = 0;
        start();
    }

    inline static void set_pwm_freq(uint32_t freq) {
        static_assert( (CHANNEL >= 1) && (CHANNEL <= 4), "CHANNEL fail");
        
        TIMER(BASE)->ARR = (__1M__ / freq) - 1;
        if constexpr (CHANNEL == 1) {
            TIMER(BASE)->CCR1 = __1M__ / freq / 2;
        } else if constexpr (CHANNEL == 2) {
            TIMER(BASE)->CCR2 = __1M__ / freq / 2;
        } else if constexpr (CHANNEL == 3) {
            TIMER(BASE)->CCR3 = __1M__ / freq / 2;
        } else if constexpr (CHANNEL == 4) {
            TIMER(BASE)->CCR4 = __1M__ / freq / 2;
        }
    }

    inline static void start() {
        TIMER(BASE)->EGR = TIM_EGR_UG;
        TIMER(BASE)->CR1 = TIM_CR1_CEN;
    }

    inline static void stop() {
        TIMER(BASE)->CR1 = 0x00;
        TIMER(BASE)->DIER = 0x00;
    }
};



}

#endif
